﻿/*  
	------------------------------------------------------------------------
	 NQ Core Library
	
	 Homepage: http://www.awzhome.de/
	------------------------------------------------------------------------
	
	This Source Code Form is subject to the terms of the Mozilla Public
	License, v. 2.0. If a copy of the MPL was not distributed with this
	file, You can obtain one at http://mozilla.org/MPL/2.0/.
   
	The Original Code is code of NQ Core Library.

	The Initial Developer of the Original Code is Andreas Weizel.
	Portions created by the Initial Developer are
	Copyright (C) 2012 Andreas Weizel. All Rights Reserved.
	
	Contributor(s): (none)
	
	------------------------------------------------------------------------
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Reflection;

namespace AWZhome.NQ.Core.Serialization
{
	/// <summary>
	/// Helper class for serialization and deserialization between
	/// <see cref="AWZhome.NQ.Core.INQComponentInfo">INQComponentInfo</see>-
	/// and <see cref="AWZhome.NQ.Core.INQServiceInfo">INQServiceInfo</see>-based
	/// objects and XML structures.
	/// </summary>
	public class NQComponentInfoSerializer
	{
		private Encoding _encoding;

		/// <summary>
		/// Creates a new instance of the serializer class.
		/// </summary>
		public NQComponentInfoSerializer()
			: this(Encoding.UTF8)
		{
		}

		/// <summary>
		/// Creates a new instance of the serializer class and sets its output encoding.
		/// </summary>
		/// <param name="encoding">Encoding for the XML output.</param>
		public NQComponentInfoSerializer(Encoding encoding)
		{
			_encoding = encoding;
		}

		/// <summary>
		/// Reads XML component definition from a stream and creates
		/// <see cref="AWZhome.NQ.Core.INQComponentInfo">INQComponentInfo</see> and
		/// <see cref="AWZhome.NQ.Core.INQServiceInfo">INQServiceInfo</see>-based
		/// objects from it.
		/// </summary>
		/// <param name="sourceStream">Source stream with XML definition data.</param>
		/// <param name="componentInfo">Receives a reference to an info object describing the component.</param>
		/// <param name="serviceInfos">Receives a reference to an array of service information objects.</param>
		/// <returns><c>True</c>, if the component information could be read successfully, <c>false</c> otherwise.</returns>
		public bool ReadComponentInfo(Stream sourceStream, out INQComponentInfo componentInfo, out INQServiceInfo[] serviceInfos)
		{
			NQComponentInfo tempCompInfo = new NQComponentInfo();
			List<INQServiceInfo> servInfoTempArray = new List<INQServiceInfo>();

			// We don't catch exceptions here, let the caller do that
			//try
			//{
			XmlSerializer serializer = new XmlSerializer(typeof(XmlStub.DefinitionRootType));
			XmlStub.DefinitionRootType compDef = (XmlStub.DefinitionRootType) serializer.Deserialize(sourceStream);

			tempCompInfo.Name = compDef.name;
			tempCompInfo.DisplayName = compDef.displayname;
			tempCompInfo.DisplayVersion = compDef.displayversion;
			tempCompInfo.UpdaterURL = compDef.updaterurl;

			// Resource definitions
			if (compDef.resources != null)
			{
				if (compDef.resources.nontranslatedresource != null)
				{
					XmlStub.ResourceType resource = compDef.resources.nontranslatedresource;
					tempCompInfo.NonTranslatedResKey = resource.key;
					tempCompInfo.NonTranslatedRes = resource.@namespace;
					tempCompInfo.NonTranslatedPath = resource.path;
				}
				if (compDef.resources.translatedresource != null)
				{
					XmlStub.ResourceType resource = compDef.resources.translatedresource;
					tempCompInfo.TranslatedResKey = resource.key;
					tempCompInfo.TranslatedRes = resource.@namespace;
				}
			}

			// Requirements
			if (compDef.requirements != null)
			{
				foreach (var req in compDef.requirements)
				{
					NQComponentRequirement compreq = new NQComponentRequirement();
					compreq.ComponentName = req.name;
					compreq.Version = new Version(req.version);
					compreq.Condition = this.GetConditionFromXml(req.@operator);
				}
			}

			// Compatibility information
			if (compDef.compatibility != null)
			{
				foreach (var comp in compDef.compatibility)
				{
					NQComponentRequirement compCompat = new NQComponentRequirement();
					compCompat.Version = new Version(comp.version);
					if (comp.operatorSpecified)
					{
						compCompat.Condition = this.GetConditionFromXml(comp.@operator);
					}
					else
					{
						compCompat.Condition = NQCondition.Equal;
					}
				}
			}

			// Assembly information
			if (compDef.assemblies != null)
			{
				List<string> partAssemblyNames = new List<string>();
				//foreach (var assemblyDef in compDef.assemblies)
				//{
				//  // Collect assembly full names
				//  if (assemblyDef.ismainSpecified && assemblyDef.ismain)
				//  {
				//    tempCompInfo.MainAssemblyFullName = Path.GetFileNameWithoutExtension(assemblyDef.file);
				//  }
				//  else
				//  {
				//    partAssemblyNames.Add(Path.GetFileNameWithoutExtension(assemblyDef.file));
				//  }
				//}

				foreach (var assemblyDef in compDef.assemblies)
				{
					// Collect assembly full names
					AssemblyName assemblyName = null;
					string assemblyFullName = String.Empty;
					try
					{
						assemblyName = AssemblyName.GetAssemblyName(assemblyDef.file);
						assemblyFullName = assemblyName.Name;
					}
					catch (Exception)
					{
						NQServiceManager.Instance.LogMessage(NQLogType.Error,
							"NQComponentInfoSerializer: Assembly " + assemblyDef + " not found or not accessible.");
						continue;
					}

					if (assemblyDef.ismainSpecified && assemblyDef.ismain)
					{
						tempCompInfo.MainAssemblyFullName = assemblyFullName;
					}
					else
					{
						partAssemblyNames.Add(assemblyFullName);
					}

					foreach (var serviceDef in assemblyDef.services)
					{
						NQServiceInfo servInfo = new NQServiceInfo();
						servInfo.ServiceInterfaceName = serviceDef.name;
						servInfo.ServiceTypeName = serviceDef.@class;
						servInfo.ParentComponent = tempCompInfo.Name;
						if (serviceDef.categorySpecified)
						{
							servInfo.ServiceCategory = this.GetHostModeFromXml(serviceDef.category);
						}
						else
						{
							servInfo.ServiceCategory = NQHostMode.General;
						}
						servInfo.SingleInstance = (serviceDef.singleinstanceSpecified && serviceDef.singleinstance);

						// Read invokable service settings
						servInfo.InvokeMethodName = serviceDef.invokemethod;
						servInfo.QuitMethodName = serviceDef.quitmethod;

						// Handle substitutes
						if (serviceDef.substitutions != null)
						{
							servInfo.SubstituteNames = new string[serviceDef.substitutions.Length];
							serviceDef.substitutions.CopyTo(servInfo.SubstituteNames, 0);
						}

						// Handle AttachLists
						if (serviceDef.attachedto != null)
						{
							servInfo.AttachedTo = new string[serviceDef.attachedto.Length];
							serviceDef.attachedto.CopyTo(servInfo.AttachedTo, 0);
						}

						// Handle requirements
						if (serviceDef.requirements != null)
						{
							foreach (var req in serviceDef.requirements)
							{
								NQComponentRequirement compreq = new NQComponentRequirement();
								compreq.ComponentName = req.name;
								compreq.Version = new Version(req.version);
								compreq.Condition = this.GetConditionFromXml(req.@operator);
							}
						}

						// Handle auto-injection
						if (serviceDef.autoinjection != null)
						{
							var autoInjectionDefCollection = serviceDef.autoinjection;
							if (autoInjectionDefCollection.Items != null)
							{
								List<INQAutoInjection> autoInjections = new List<INQAutoInjection>();

								for (int i = 0; i < autoInjectionDefCollection.Items.Length; i++)
								{
									var autoInjectionDef = autoInjectionDefCollection.Items[i];
									NQAutoInjection autoInjection = new NQAutoInjection();

									// Injection source depends on the name of the XML element (<list> or <service>)
									if (autoInjectionDefCollection.ItemsElementName[i] == XmlStub.ItemsChoiceType.list)
									{
										autoInjection.InjectFromAttachList = true;
										autoInjection.BoundAttachList = autoInjectionDef.name;
									}
									else
									{
										autoInjection.InjectFromAttachList = false;
										autoInjection.BoundServiceName = autoInjectionDef.name;
									}

									autoInjection.InjectAsDependent = (autoInjectionDef.asdependentSpecified && autoInjectionDef.asdependent);
									autoInjection.Overridable = (autoInjectionDef.overridableSpecified && autoInjectionDef.overridable);
									autoInjection.ServiceInterfaceName = autoInjectionDef.type;

									autoInjections.Add(autoInjection);
								}

								servInfo.AutoInjections = new INQAutoInjection[autoInjections.Count];
								autoInjections.CopyTo(servInfo.AutoInjections);
							}
						}

						servInfoTempArray.Add(servInfo);
					}
				}

				if (partAssemblyNames.Count > 0)
				{
					tempCompInfo.PartAssembliesFullNames = new string[partAssemblyNames.Count];
					partAssemblyNames.CopyTo(tempCompInfo.PartAssembliesFullNames);
				}
			}

			componentInfo = tempCompInfo;
			serviceInfos = new INQServiceInfo[servInfoTempArray.Count];
			servInfoTempArray.CopyTo(serviceInfos);
			return true;
			//}
			//catch (Exception)
			//{
			//  // Component information could not be retrieved from stream
			//  componentInfo = null;
			//  serviceInfos = null;
			//  return false;
			//}
		}

		/// <summary>
		/// Writes XML component definition created from
		/// <see cref="AWZhome.NQ.Core.INQComponentInfo">INQComponentInfo</see>-
		/// and <see cref="AWZhome.NQ.Core.INQServiceInfo">INQServiceInfo</see>-based
		/// objects.
		/// </summary>
		/// <param name="destStream">Stream to write the XML data to.</param>
		/// <param name="componentInfo">Component definition to serialize.</param>
		/// <param name="serviceInfos">Service definitions to serialize.</param>
		/// <returns><c>True</c>, if successful, <c>false</c> otherwise.</returns>
		/// <remarks>
		/// The method only serializes the services, that belong to the passed component
		/// (see <see cref="AWZhome.NQ.Core.INQServiceInfo.ComponentName">INQServiceInfo.ComponentName</see> property).
		/// </remarks>
		public bool WriteComponentInfo(Stream destStream, INQComponentInfo componentInfo, INQServiceInfo[] serviceInfos)
		{
			if ((componentInfo == null) || (serviceInfos == null))
			{
				return false;
			}

			try
			{
				XmlStub.DefinitionRootType xmlCompInfo = new AWZhome.NQ.Core.Serialization.XmlStub.DefinitionRootType();

				xmlCompInfo.name = componentInfo.Name;
				xmlCompInfo.displayname = componentInfo.DisplayName;
				xmlCompInfo.displayversion = componentInfo.DisplayVersion;
				xmlCompInfo.updaterurl = componentInfo.UpdaterURL;

				if ((componentInfo.Requires != null) && (componentInfo.Requires.Length > 0))
				{
					// Serialize requirements
					xmlCompInfo.requirements = new XmlStub.RequirementType[componentInfo.Requires.Length];
					int i = 0;
					foreach (var req in componentInfo.Requires)
					{
						XmlStub.RequirementType xmlReq = new AWZhome.NQ.Core.Serialization.XmlStub.RequirementType();
						xmlReq.name = req.ComponentName;
						xmlReq.@operator = this.GetXmlFromCondition(req.Condition);
						xmlReq.operatorSpecified = true;
						xmlReq.version = req.Version.ToString();
						xmlCompInfo.requirements[i] = xmlReq;

						i++;
					}
				}

				if ((componentInfo.Compatibility != null) && (componentInfo.Compatibility.Length > 0))
				{
					// Serialize compatibility settings
					xmlCompInfo.compatibility = new XmlStub.CompatibilityConditionType[componentInfo.Compatibility.Length];
					int i = 0;
					foreach (var compat in componentInfo.Compatibility)
					{
						XmlStub.CompatibilityConditionType xmlCompat = new XmlStub.CompatibilityConditionType();
						xmlCompat.@operator = this.GetXmlFromCondition(compat.Condition);
						xmlCompat.operatorSpecified = true;
						xmlCompat.version = compat.Version.ToString();
						xmlCompInfo.compatibility[i] = xmlCompat;

						i++;
					}
				}

				// Serialize resources
				xmlCompInfo.resources = new XmlStub.DefinitionRootTypeResources();
				if (!String.IsNullOrEmpty(componentInfo.NonTranslatedResKey))
				{
					XmlStub.ResourceType nonTranslatedResource = new XmlStub.ResourceType();
					nonTranslatedResource.key = componentInfo.NonTranslatedResKey;
					nonTranslatedResource.@namespace = componentInfo.NonTranslatedRes;
					nonTranslatedResource.path = componentInfo.NonTranslatedPath;
					xmlCompInfo.resources.nontranslatedresource = nonTranslatedResource;
				}
				if (!String.IsNullOrEmpty(componentInfo.TranslatedResKey))
				{
					XmlStub.ResourceType translatedResource = new XmlStub.ResourceType();
					translatedResource.key = componentInfo.TranslatedResKey;
					translatedResource.@namespace = componentInfo.TranslatedRes;
					xmlCompInfo.resources.translatedresource = translatedResource;
				}

				// We save the assemblies of this component in a hash map
				Dictionary<string, XmlStub.AssemblyType> assemblyTagList = new Dictionary<string, XmlStub.AssemblyType>();
				Dictionary<string, List<XmlStub.ServiceType>> serviceTagLists = new Dictionary<string, List<XmlStub.ServiceType>>();

				// Get assembly information from component metadata
				List<Assembly> tempAllAssembliesList = new List<Assembly>();
				if (componentInfo.MainAssembly != null)
				{
					tempAllAssembliesList.Add(componentInfo.MainAssembly);
				}
				if ((componentInfo.PartAssemblies != null) && (componentInfo.PartAssemblies.Length > 0))
				{
					tempAllAssembliesList.AddRange(componentInfo.PartAssemblies);
				}

				for (int i = 0; i < tempAllAssembliesList.Count; i++)
				{
					Assembly servAssembly = tempAllAssembliesList[i];
					string hashkey = servAssembly.FullName;
					if (!assemblyTagList.ContainsKey(hashkey))
					{
						XmlStub.AssemblyType xmlAssembly = new XmlStub.AssemblyType();
						xmlAssembly.file = Path.GetFileName(servAssembly.Location);
						xmlAssembly.ismain = (componentInfo.MainAssembly.Location == servAssembly.Location);
						xmlAssembly.ismainSpecified = true;
						assemblyTagList.Add(hashkey, xmlAssembly);
						serviceTagLists.Add(hashkey, new List<XmlStub.ServiceType>());
					}
				}

				// Run through all passed services
				foreach (var servInfo in serviceInfos)
				{
					if (servInfo.ParentComponent == componentInfo.Name)
					{
						// Create a new assembly tag, if not already done
						Assembly servAssembly = servInfo.ServiceType.Assembly;
						string hashkey = servAssembly.FullName;

						// Create the service structure
						XmlStub.ServiceType xmlService = new XmlStub.ServiceType();
						xmlService.name = servInfo.ServiceInterface.FullName;
						xmlService.@class = servInfo.ServiceType.FullName;
						xmlService.category = this.GetXmlFromHostMode(servInfo.ServiceCategory);
						xmlService.categorySpecified = true;
						xmlService.singleinstance = servInfo.SingleInstance;
						xmlService.singleinstanceSpecified = true;

						// Invoke and Quit methods
						if (servInfo.IsInvokable)
						{
							if (servInfo.InvokeMethod != null)
							{
								xmlService.invokemethod = servInfo.InvokeMethod.Name;
							}
							if (servInfo.QuitMethod != null)
							{
								xmlService.quitmethod = servInfo.QuitMethod.Name;
							}
						}

						// Handle substitutions and service attachments
						if (servInfo.Substitutes != null)
						{
							xmlService.substitutions = servInfo.Substitutes
								.Select<Type, string>(t => t.FullName + ", " + t.Assembly.GetName().Name)
								.ToArray<string>();
						}
						xmlService.attachedto = servInfo.AttachedTo;

						// Handle requirements
						if ((servInfo.Requires != null) && (servInfo.Requires.Length > 0))
						{
							xmlService.requirements = new XmlStub.RequirementType[servInfo.Requires.Length];
							int i = 0;
							foreach (var req in servInfo.Requires)
							{
								XmlStub.RequirementType xmlReq = new XmlStub.RequirementType();
								xmlReq.name = req.ComponentName;
								xmlReq.@operator = this.GetXmlFromCondition(req.Condition);
								xmlReq.operatorSpecified = true;
								xmlReq.version = req.Version.ToString();
								xmlService.requirements[i] = xmlReq;

								i++;
							}
						}

						// Handle auto-injections settings
						if ((servInfo.AutoInjections != null) && (servInfo.AutoInjections.Length > 0))
						{
							XmlStub.ServiceTypeAutoinjection xmlAutoInjectionRoot = new XmlStub.ServiceTypeAutoinjection();
							xmlAutoInjectionRoot.Items = new XmlStub.AutoInjectionType[servInfo.AutoInjections.Length];
							xmlAutoInjectionRoot.ItemsElementName = new XmlStub.ItemsChoiceType[servInfo.AutoInjections.Length];
							int i = 0;
							foreach (var autoInjection in servInfo.AutoInjections)
							{
								XmlStub.AutoInjectionType xmlAutoInjection = new XmlStub.AutoInjectionType();
								if (autoInjection.InjectFromAttachList)
								{
									xmlAutoInjection.name = autoInjection.BoundAttachList;
								}
								else
								{
									xmlAutoInjection.name = autoInjection.BoundService.FullName;
								}
								xmlAutoInjection.overridableSpecified = true;
								xmlAutoInjection.overridable = autoInjection.Overridable;
								xmlAutoInjection.asdependentSpecified = true;
								xmlAutoInjection.asdependent = autoInjection.InjectAsDependent;
								xmlAutoInjection.type = autoInjection.Interface.FullName;

								xmlAutoInjectionRoot.ItemsElementName[i] =
									autoInjection.InjectFromAttachList ? XmlStub.ItemsChoiceType.list : XmlStub.ItemsChoiceType.service;
								xmlAutoInjectionRoot.Items[i] = xmlAutoInjection;

								i++;
							}

							xmlService.autoinjection = xmlAutoInjectionRoot;
						}

						// Now save the XML object instance
						serviceTagLists[hashkey].Add(xmlService);
					}
				}

				// Add assemblies and services to component XML
				xmlCompInfo.assemblies = new XmlStub.AssemblyType[assemblyTagList.Count];
				int j = 0;
				foreach (KeyValuePair<string, XmlStub.AssemblyType> kv in assemblyTagList)
				{
					xmlCompInfo.assemblies[j] = kv.Value;

					kv.Value.services = new XmlStub.ServiceType[serviceTagLists[kv.Key].Count];
					int k = 0;
					foreach (XmlStub.ServiceType xmlService in serviceTagLists[kv.Key])
					{
						kv.Value.services[k] = xmlService;
						k++;
					}

					j++;
				}

				// Now write the XML structure to the stream
				XmlSerializer serializer = new XmlSerializer(typeof(XmlStub.DefinitionRootType));
				serializer.Serialize(destStream, xmlCompInfo);

				return true;
			}
			catch (Exception)
			{
				// Writing XML data was not successful
				return false;
			}
		}

		private NQCondition GetConditionFromXml(XmlStub.VersionContraintOperatorType operatorType)
		{
			switch (operatorType)
			{
				case XmlStub.VersionContraintOperatorType.equal:
					return NQCondition.Equal;
				case XmlStub.VersionContraintOperatorType.greater:
					return NQCondition.Greater;
				case XmlStub.VersionContraintOperatorType.lower:
					return NQCondition.Lower;
				case XmlStub.VersionContraintOperatorType.greaterorequal:
					return NQCondition.Greater | NQCondition.Equal;
				case XmlStub.VersionContraintOperatorType.lowerorequal:
					return NQCondition.Lower | NQCondition.Equal;
				default:
					return NQCondition.Equal;
			}
		}

		private XmlStub.VersionContraintOperatorType GetXmlFromCondition(NQCondition condition)
		{
			switch (condition)
			{
				case NQCondition.Equal:
					return XmlStub.VersionContraintOperatorType.equal;
				case NQCondition.Greater:
					return XmlStub.VersionContraintOperatorType.greater;
				case NQCondition.Lower:
					return XmlStub.VersionContraintOperatorType.lower;
				case NQCondition.Equal | NQCondition.Greater:
					return XmlStub.VersionContraintOperatorType.greaterorequal;
				case NQCondition.Equal | NQCondition.Lower:
					return XmlStub.VersionContraintOperatorType.lowerorequal;
				default:
					return XmlStub.VersionContraintOperatorType.equal;
			}
		}

		private NQHostMode GetHostModeFromXml(XmlStub.HostModeType hostModeType)
		{
			switch (hostModeType)
			{
				case AWZhome.NQ.Core.Serialization.XmlStub.HostModeType.general:
					return NQHostMode.General;
				case AWZhome.NQ.Core.Serialization.XmlStub.HostModeType.gui:
					return NQHostMode.GUI;
				case AWZhome.NQ.Core.Serialization.XmlStub.HostModeType.console:
					return NQHostMode.Console;
				case AWZhome.NQ.Core.Serialization.XmlStub.HostModeType.winservice:
					return NQHostMode.WindowsService;
				case AWZhome.NQ.Core.Serialization.XmlStub.HostModeType.webserver:
					return NQHostMode.WebServer;
				case AWZhome.NQ.Core.Serialization.XmlStub.HostModeType.browser:
					return NQHostMode.Browser;
				default:
					return NQHostMode.General;
			}
		}

		private XmlStub.HostModeType GetXmlFromHostMode(NQHostMode hostMode)
		{
			switch (hostMode)
			{
				case NQHostMode.General:
					return AWZhome.NQ.Core.Serialization.XmlStub.HostModeType.general;
				case NQHostMode.GUI:
					return AWZhome.NQ.Core.Serialization.XmlStub.HostModeType.gui;
				case NQHostMode.Console:
					return AWZhome.NQ.Core.Serialization.XmlStub.HostModeType.console;
				case NQHostMode.WindowsService:
					return AWZhome.NQ.Core.Serialization.XmlStub.HostModeType.winservice;
				case NQHostMode.WebServer:
					return AWZhome.NQ.Core.Serialization.XmlStub.HostModeType.webserver;
				case NQHostMode.Browser:
					return AWZhome.NQ.Core.Serialization.XmlStub.HostModeType.browser;
				default:
					return AWZhome.NQ.Core.Serialization.XmlStub.HostModeType.general;
			}
		}
	}
}
